// SERVER PROGRAM

#include <stdio.h>      /* for printf() and fprintf() */
#include <sys/socket.h> /* for socket() and bind() */
#include <arpa/inet.h>  /* for sockaddr_in and inet_ntoa() */
#include <stdlib.h>     /* for atoi() and exit() */
#include <string.h>     /* for memset() */
#include <unistd.h>     /* for close() */

#include "standardPacket.h"
#include "clientTable.h"

#define ECHOMAX 255     /* Longest string to echo */

void DieWithError(const char *errorMessage) /* External error handling function */
{
    perror(errorMessage);
    exit(1);
}

int main(int argc, char *argv[])
{
    int sock;                        /* Socket */
    struct sockaddr_in echoServAddr; /* Local address */
    struct sockaddr_in echoClntAddr; /* Client address */
    unsigned int cliAddrLen;         /* Length of incoming message */
    char echoBuffer[ECHOMAX];        /* Buffer for echo string */
    unsigned short echoServPort;     /* Server port */
    int recvMsgSize;                 /* Size of received message */
    char addr_str[INET_ADDRSTRLEN];
    bool process_req = false;                

    clientTable* serverTable = new clientTable(); // table to store all processors and clients
    char data_string[6];                   // The string that will hold our data

    if (argc != 2)         /* Test for correct number of parameters */
    {
        fprintf(stderr,"Usage:  %s <UDP SERVER PORT>\n", argv[0]);
        exit(1);
    }

    echoServPort = atoi(argv[1]);  /* First arg:  local port */

    /* Create socket for sending/receiving datagrams */
    if ((sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0)
        DieWithError("socket() failed");

    /* Construct local address structure */
    memset(&echoServAddr, 0, sizeof(echoServAddr));   /* Zero out structure */
    echoServAddr.sin_family = AF_INET;                /* Internet address family */
    echoServAddr.sin_addr.s_addr = htonl(INADDR_ANY); /* Any incoming interface */
    echoServAddr.sin_port = htons(echoServPort);      /* Local port */

    /* Bind to the local address */
    if (bind(sock, (struct sockaddr *) &echoServAddr, sizeof(echoServAddr)) < 0)
        DieWithError("bind() failed");
  
	Request *myRequest = new Request;
    tableEntry *currentReq;
    int cNum;
    int cReq;
    int cInc;
    int sndMsgSize = (6*(sizeof(char)));
    bool sendReq = true;

    for (;;) /* Run forever */
    {
        /* Set the size of the in-out parameter */
        cliAddrLen = sizeof(echoClntAddr);

        /* Block until receive message from a client */
        if ((recvMsgSize = recvfrom(sock, myRequest, sizeof(Request), 0,
            (struct sockaddr *) &echoClntAddr, &cliAddrLen)) < 0)
            DieWithError("recvfrom() failed");
        else
        {
            // Put the address of the incoming request in to the char[] addr_str
            inet_ntop(AF_INET, &(echoClntAddr.sin_addr), addr_str, INET_ADDRSTRLEN);

            cNum = myRequest->client;
            cInc = myRequest->inc;
            cReq = myRequest->req;
                
            // This is the first type of failure. if the function
            // returns true, drop the packet.
            if( randomSuccess(10) )
            {
                cout << "\nERROR: the Server dropped the packet\n";
                sendReq = false;
                process_req = false;
            }

            else
            {
                // Second type of failure, 
                if( randomSuccess(10) )    
                    sendReq = false;

                // No error we send the reqeust
                else
                    sendReq = true;

                // If the (P, I, C) triple doesn't exist, add it to the table and
                // process the request
                if( !(serverTable->exists(addr_str, cInc, cNum)) )
                {
                    serverTable->addEntry(addr_str, cInc, cReq, cNum);
                    process_req = true;
                }

            // If the (P, I, C) triple does exist, check the request number
            // and process the request accordingly
                else
                {
                    currentReq = serverTable->getEntry(addr_str, cInc, cNum);
                    if( currentReq->getReq() == cReq )
                    {
                        process_req = true;
                    }

                    else if( currentReq->getReq() < cReq )
                    {
                        currentReq->setReq(cReq);
                        process_req = true;
                    }

                    else
                    {
                        process_req = false;
                    }
                }
            }

            if ( process_req )
            {
                for( int i = 4; i > 0; i-- )
                {
                    data_string[i] = data_string[i-1];
                }
            }
         
            data_string[0] = myRequest->c;
        }
        if( sendReq )
        {
            printf("Handling client %s\n", inet_ntoa(echoClntAddr.sin_addr));

        // Send the permutated string back to the client
            if (sendto(sock, myRequest, sndMsgSize, 0, (struct sockaddr *) &echoClntAddr, sizeof(echoClntAddr)) != sndMsgSize)
                DieWithError("sendto() sent a different number of bytes than expected");
        }
    }
    /* NOT REACHED */
}
